// @(#)alarm.cc
// Time-stamp: <2009-02-26 00:58:38 root>
// Copyright 2008 Julian Qian
// Author: root@localhost.localdomain
// Version: $Id: alarm.cc,v 0.0 2008/11/16 18:39:49 root Exp $
// Keywords: 

#include "alarm.hh"
#include "server.hh"
#include "proto.hh"
#include "log.hh"
#include "soap/wrapesms.hh"
#include "db.hh"
#include "mutex.hh"
#include "excep.hh"
#include "heart.hh"
#include "func.hh"
#include "inifile.hh"

#include <time.h>

using std::string;


Alarm* Alarm::_theAlarm = NULL;

Alarm*
Alarm::theAlarm(){
    if(NULL == _theAlarm){
        _theAlarm = new Alarm();
    }
    
    return _theAlarm;
}

void
Alarm::endAlarm(){
    delete _theAlarm;
}

Alarm::Alarm(){
    try{
        _we = Wrapesms::theWrapesms();
    }
    catch(const ConfigError& ex){
        throw AlarmInitFailed(ex.what());
    }
    // Don't catch this, just abort
    catch(const LoginFailed &ex){
        LFatal(ex.what());
        
        throw AlarmInitFailed(ex.what());
    }
    catch(const EsmsDisabled &ex){
        LInfo(ex.what());
    }
    
    try{
        // _db = Db::theDb();
        _db = new Db;
    }
    catch(const mysqlpp::ConnectionFailed& ex){
        throw AlarmInitFailed(ex.what());
    }

    Config *cnf = Config::theConfig(GEN_CONF_FILE);

    _wMsg["DEFAULT"]   = cnf->read("WARNING","DEFAULT");
    _wMsg["URGENCY"]   = cnf->read("WARNING","URGENCY");
    _wMsg["DOOR"]      = cnf->read("WARNING","DOOR");
    _wMsg["INFRA"]     = cnf->read("WARNING","INFRA");
    _wMsg["HELP"]      = cnf->read("WARNING","HELP");
    _wMsg["SMOKE"]     = cnf->read("WARNING","SMOKE");
    _wMsg["GAS"]       = cnf->read("WARNING","GAS");
    _wMsg["GLASS"]     = cnf->read("WARNING","GLASS");
    _wMsg["WATER"]     = cnf->read("WARNING","WATER");
    _wMsg["DEFEN"]     = cnf->read("WARNING","DEFEN");
    _wMsg["UNDEF"]     = cnf->read("WARNING","UNDEF");
    _wMsg["DEFEN_RES"] = cnf->read("WARNING","DEFEN_RES");
    _wMsg["UNDEF_RES"] = cnf->read("WARNING","UNDEF_RES");

    _ht = Heart::theHeart();

    // _devList = new DEVID_PAIR;
    // _alarmQueue = new DEVID_PAIR;

    _mutdev = new MutEX(FAST);
}

Alarm::~Alarm(){
    // delete _devList;
    // delete _alarmQueue;
    
    delete _db;
    delete _mutdev;
}

// void
// Alarm::runloop(){
//     // two timer
//     // 1) send alarm to phone by device request, alarmQueue
//     // send_phone_alarm();
//     // 2) send alarm to device by phone order, devList
//     // send_dev_alarm();
//     // _ds->engine(static_cast<Sigfunc>(&dispatcher), 5);
//     engine(CAST_(&Alarm::dispatcher), 5);
// }

// function for Dispatch::engine((void)func(int))
// void
// Alarm::dispatcher(int signo){
//     Thread thd1, thd2;

//     thd1.run((Functype)DISPATCH_send_phone_alarm, NULL);
//     thd2.run((Functype)DISPATCH_send_dev_alarm, NULL);
// }

void
Alarm::queue_alarm(devid_t devid, alarm_t alarm){
    LDebug("devid->:" << devid << ", alarm->" << (int)alarm);

    // MUTEX_LOCK(_mutdev);
    
    _alarmQueue.insert(DEVID_PAIR::value_type(devid, alarm));
    // _alarmQueue.insert(std::make_pair(devid, alarm));

    // MUTEX_UNLOCK(_mutdev);    
}

void Alarm::queue_ctrl(devid_t devid, alarm_t alarm){
    LDebug("CTL) devid->:" << devid << ", alarm->" << (int)alarm);
    _devList.insert(DEVID_PAIR::value_type(devid, alarm));
}

void
Alarm::send_phone_alarm(){
    // LDebug("alarm to phone loop ...");
    if(! _alarmQueue.empty()){
        send_phone_alarm_(_alarmQueue);
    }
    
    try{
        LDebug("putMsg once");
        _we->putMsg();
    }
    catch(const PutMsgFailed& ex){
        LFatal(ex.what());
    }
    catch(const EsmsDisabled& ex){
                
    }    
}

void
Alarm::send_phone_alarm_(DEVID_PAIR &dp_list){

    LDebug("Travel alarm phone list:");

    // Mutex alarm queue, when sned it
    // TODO: maybe this mutex caused dead-lock. 2009-01-05
    // MUTEX_LOCK(_mutdev);
    
    try{
        DEVID_PAIR::iterator itr;
        string sql = "select p.*,d.addr from phone p left join device d on d.devid = p.devid where p.devid IN (";
        for(itr = dp_list.begin(); itr != dp_list.end(); itr++){
            
            // LDebug("devid->" << itr->first);
            
            sql += itoa(itr->first);
            sql += ",";
            
            // dp_list.erase(itr++);
        }
        sql += "0);";
        
        mysqlpp::Connection* conn = _db->con();
        mysqlpp::Query query = conn->query(sql);
        
        LDebug("--->" << query.str() << "<---");
        
        mysqlpp::StoreQueryResult res = query.store();
        
        if(res){
            string mobile;
            string warning;
            string addr;
            string time_stamp(timestr(time(NULL)));
            int type;
            int alarm_type;
            devid_t devid;
            for(size_t i = 0; i < res.num_rows(); i++){
                devid = (devid_t)res[i]["devid"];
                addr = (string)res[i]["addr"];
                alarm_type = (int)dp_list[devid];

                warning.clear();
                
                switch(alarm_type){
                case ALARM_MANUA:
                    if((int)res[i]["wurgency"]){
                        warning = _wMsg["URGENCY"];
                    }
                    break;
                case ALARM_DOORR:
                    if((int)res[i]["wdoor"]){
                        warning = _wMsg["DOOR"];
                    }
                    break;                    
                case ALARM_INFRA:
                    if((int)res[i]["winfra"]){
                        warning = _wMsg["INFRA"];
                    }
                    break;                    
                case ALARM_HELPP:
                    if((int)res[i]["whelp"]){
                        warning = _wMsg["HELP"];
                    }
                    break;                    
                case ALARM_SMOKE:
                    if((int)res[i]["wsmoke"]){
                        warning = _wMsg["SMOKE"];
                    }
                    break;                    
                case ALARM_GASSS:
                    if((int)res[i]["wgas"]){
                        warning = _wMsg["GAS"];
                    }
                    break;                    
                case ALARM_GLASS:
                    if((int)res[i]["wglass"]){
                        warning = _wMsg["GLASS"];
                    }
                    break;                    
                case ALARM_WATER:
                    if((int)res[i]["wwater"]){
                        warning = _wMsg["WATER"];
                    }
                    break;                    
                case ALARM_DEFENSE:
                    if((int)res[i]["wdefen"]){
                        warning = _wMsg["DEFEN"];
                    }
                    break;
                case ALARM_DEFEN_RES:
                    if((int)res[i]["wdefen_res"]){
                        warning = _wMsg["DEFEN_RES"];
                    }
                    break;                    
                case ALARM_UNDEF_RES:
                    if((int)res[i]["wundef_res"]){
                        warning = _wMsg["UNDEF_RES"];
                    }
                    break;
                }
                if(warning.empty()){
                    warning = _wMsg["DEFAULT"];
                }

                warning += "(";
                warning += addr;
                warning += ",";
                warning += time_stamp;
                warning += ")";

                mobile = (string)res[i]["mobile"];
                
                LDebug("mobile:"<< mobile << ", alarm:" << alarm_type <<", warning:" << warning);

                _we->queue_msg(mobile, warning);
            }
            /// debug
            // MsgVector::iterator itr;
            // for(itr = send_list.begin(); itr!=send_list.end(); itr++){
            //     LDebug("send_list item: " << itr->first <<"," <<itr->second);
            // }
            ///

        }

        _db->end(conn);

        // clean alarm queue
        dp_list.clear();
        LDebug("dp_list END size:" << dp_list.size());
    }
    catch(const mysqlpp::BadQuery& er){
        LFatal( "Fetch phone number from database failed!" );
    }

    // MUTEX_UNLOCK(_mutdev);
}

string
Alarm::parse_devid(string str){
    // format1: bf,devid
    // format2: bf devid
    string::size_type idx = str.find(",");
    if(idx == string::npos){
        idx = str.find(" ");
    }
    string devid = str.substr(idx+1);
    return devid;
}

alarm_t
Alarm::parse_alarm(string str){
    // format1: bf,devid
    // format2: bf devid
    string::size_type idx = str.find(",");
    if(idx == string::npos){
        idx = str.find(" ");
    }
    string alastr = str.substr(0, idx);
    return alarm_ctl(alastr.c_str());
}

DEVID_PAIR*
Alarm::get_devlist(){
    try{
        MsgVector* recv_list = _we->getMsg();
        
        // phone -> devid -> ip
        MsgVector::iterator itr;
        string sql, devid_str;
        int cnt;
        alarm_t alarm;
        devid_t devid;
        for(itr = recv_list->begin(); itr != recv_list->end(); ++itr){
            devid_str = parse_devid(itr->message_str());
            sql = "select devid from phone where mobile = '"+ itr->mobile_str() +"' and devid = '"+ devid_str +"' limit 1";
            try{
                mysqlpp::Connection* conn = _db->con();
                mysqlpp::Query query = conn->query(sql);
                mysqlpp::SimpleResult res = query.execute();
                cnt = res.rows();
                if(cnt){
                    devid = atoi(devid_str.c_str());
                    alarm = parse_alarm(itr->message_str());
                
                    _devList.insert(DEVID_PAIR::value_type(devid, alarm));
                }
                _db->end(conn);
            }
            catch(const mysqlpp::BadQuery& er){
                LFatal( "Fetch devid from database failed:"<< itr->mobile_str() );
            }
        }

        _we->clearGetMsgResult();
    
    }
    catch(const GetMsgFailed& ex){
        throw GetDevListException(ex.what());
    }
    catch(const EsmsDisabled& ex){
        
    }
    return &_devList;
}

/*
DEVID_PAIR*
Alarm::get_devlist(){
    try{
        MsgVector* recv_list = _we->getMsg();
        
        // phone -> devid -> ip
        MsgVector::iterator itr;
        string sql = "select devid, mobile from phone where mobile IN (";
        for(itr = recv_list->begin(); itr != recv_list->end(); itr++){
            sql += "'" + itr->first + "',";
        }
        sql += "'');";

        try{
            mysqlpp::Connection* conn = _db->con();
            mysqlpp::Query query = conn->query(sql);

            LDebug("get_devlist: " <<sql);
        
            // mysqlpp::StoreQueryResult res = query.store();
            if(mysqlpp::UseQueryResult res = query.use()){
                string mobile;
                devid_t devid;
                alarm_t alarm;
                while(mysqlpp::Row row = res.fetch_row()){
                    mobile = (string)row["mobile"];
                    devid = (devid_t)row["devid"];
                    alarm = alarm_ctl( ((*recv_list)[mobile]).c_str() );
                
                    _devList.insert(DEVID_PAIR::value_type(devid, alarm));
                }
            }
            _db->end(conn);
        }
        catch(const mysqlpp::BadQuery& er){
            LFatal( "Fetch devid from database failed!" );
        
            _we->clearGetMsgResult();
            
            throw GetDevListException(er.what());
        }

        _we->clearGetMsgResult();
    
    }
    catch(const GetMsgFailed& ex){
        throw GetDevListException(ex.what());
    }
    catch(const EsmsDisabled& ex){
        
    }
    return &_devList;
}
*/

void
Alarm::clear_list(DEVID_PAIR& list){
    if(! list.empty()){
        DEVID_PAIR::iterator itr;
        for(itr = list.begin(); itr != list.end(); ){
            list.erase(itr++);
        }
        list.clear();
    }
}

void
Alarm::send_dev_alarm(){
    // LDebug("alarm to device loop ...");
    // LDebug("alarm to device loop1:" << _devList.size());    
    try{
        get_devlist();
    }
    catch(const GetDevListException& ex){
        LDebug("get_devlist() " << ex.what());
    }
    LDebug("alarm to device loop:" << _devList.size());
    if(! _devList.empty()){
        send_dev_alarm_(_devList);
    }
}

// Control remote device by message, such as "bf" or "cf" to set defense
// or undefense
void
Alarm::send_dev_alarm_(DEVID_PAIR& dlist){

    Server send_cli;

    devid_t devid;
    alarm_t alarm;
    int ret, seq;
    char* bptr = send_cli.get_buffer();
    
    DEVID_PAIR::iterator itr;

    LDebug("Travel alarm dev list:" << dlist.size() << ", " << _ht->proto_nums());
    for(itr = dlist.begin(); itr != dlist.end(); ){

        devid = itr->first;
        alarm = itr->second;

        LDebug("devid->" << devid);
        LDebug("Proto nums->" << _ht->proto_nums());

        Proto* pro = _ht->get_proto(devid);

        if( NULL == pro || ! pro->is_auth()){
            LDebug("This device is invaild!" );
            dlist.erase(itr++);
            continue;
        }

        // alarm: 1)defense 2)undefense
        seq = pro->get_sequence();
        bptr = pro->composite(bptr, devid, seq, SER_UPDATE, alarm);

        LDebug("dblog_record" << pro->get_stat());
        // update crosponding Proto by devid
        // ATTION: This step is just push_heart(), but omit an existent judge
        pro->buffer_init(bptr);
        _ht->push_heart(*pro);

        send_cli.set_buffer(bptr, PROTO_LENG);

        // sail off
        LDebug("Send defense to device " << IP(_ht->get_ip(devid)) << ":" << send_cli.print_hex(bptr, PROTO_LENG));
        
        if(! send_cli.Send(_ht->get_ip(devid))){
            LDebug("Send defense Failed!");
            ++itr;
            continue;
        }

        // need response to mobile phone
        queue_alarm(devid, alarm_result(alarm));
        // TODO: expired time
        /*
          send_cli.sock_timeout(3);
        
          ret = send_cli.Recv();
          if(ret < 0){
          if(errno == EWOULDBLOCK){
          // Socket timeout
          LDebug("Recv defense Timeout!");                
          }else{
          // Recvfrom failed
          LDebug("Recv defense Failed!");                
          }
          }else if(ret != PROTO_LENG ){
          // receive error
          LDebug("Recv defense Error!");                            
          }else{
          LDebug("Recv defense OK!");
          }
        */
        
        dlist.erase(itr++);            


    }
    
    dlist.clear();
}

alarm_t
Alarm::alarm_ctl(const char* pch){
    alarm_t alarm;
    if(strncasecmp(pch, DEFEN_MSG, sizeof(DEFEN_MSG)) == 0){
        alarm = CTRL_DEFEN;
    }else if(strncasecmp(pch, UNDEF_MSG, sizeof(UNDEF_MSG)) == 0){
        alarm = CTRL_UNDEF;
    }else{
        alarm = CTRL_NULL;
    }
    return alarm;
}

alarm_t
Alarm::alarm_result(alarm_t alarm){
    alarm_t alaret;
    switch(alarm){
    case CTRL_DEFEN:
        alaret = ALARM_DEFEN_RES;
        break;
    case CTRL_UNDEF:
        alaret = ALARM_UNDEF_RES;
        break;
    }
    return alaret;
}

string
Alarm::alarm_str(alarm_t alarm){
    switch(alarm){
    case ALARM_DEFEN_RES:
        return MSG_DEFEN_RES;
    case ALARM_UNDEF_RES:
        return MSG_UNDEF_RES;
    default:
        return MSG_ALARM;
    }
}
